/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.text;
import java.awt.BorderLayout;
import java.awt.Toolkit;
import java.awt.event.*;
import java.awt.datatransfer.Transferable;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.*;
import java.util.*;
import java.text.MessageFormat;
import java.awt.print.PrinterJob;
import java.awt.print.Pageable;
import java.awt.print.Printable;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.awt.print.PrinterIOException;
import java.awt.print.PrinterAbortException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Component;
import java.lang.ref.WeakReference;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import javax.swing.text.*;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;
import javax.swing.text.html.HTMLDocument;
import javax.swing.undo.CannotUndoException;
import org.openide.awt.UndoRedo;
import org.openide.actions.*;
import org.openide.TopManager;
import org.openide.NotifyDescriptor;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.OpenCookie;
import org.openide.cookies.CloseCookie;
import org.openide.cookies.SaveCookie;
import org.openide.cookies.PrintCookie;
import org.openide.filesystems.*;
import org.openide.loaders.*;
import org.openide.windows.*;
import org.openide.util.Task;
import org.openide.util.TaskListener;
import org.openide.util.WeakListener;
import org.openide.util.actions.SystemAction;
import org.openide.util.actions.CallbackSystemAction;
import org.openide.util.actions.CallableSystemAction;
import org.openide.util.actions.ActionPerformer;
import org.openide.util.datatransfer.PasteType;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
/** Support for associating an editor and a Swing {@link Document} to a data object.
* Can be assigned as a cookie to any editable data object.
* Then this data object will be capable of being opened in an editor, and there will be ways of retrieving and acting upon the Swing document which is editing it.
*
* @author Jaroslav Tulach
*/
public class EditorSupport extends OpenSupport
implements EditorCookie, OpenCookie, CloseCookie, PrintCookie {
/** Common name for editor mode. */
public static final String EDITOR_MODE = "editor"; // NOI18N
/** Task for loading the document. */
private Task loadTask;
/** Task for preparing the document. Consists for loading a document (runs loadTask),
* firing </code>stateChange</code> and
* initializing it by attaching listeners listening to document changes, such as SavingManager and
* LineSet.
*/
private Task prepareTask;
/** editor kit to work with */
private EditorKit kit;
/** original document for the kit */
private Document originalDoc;
/** document we work with */
private StyledDocument doc;
/** Non default MIME type used to editing */
private String mimeType;
/** default mime type */
private String defaultMIMEType;
/** Actions to show in toolbar */
private SystemAction[] actions;
/** The flag saying if we should listen to the document modifications */
private boolean listenToModifs = true;
/** Listener to the document changes */
private SavingManager modifL;
/** the undo/redo manager to use for this document */
private UndoRedo.Manager undoRedo;
/** Listeners for the changing of the state - document in memory X closed.
* @associates ChangeListener*/
private HashSet listeners;
/** lines set for this object */
private Line.Set lineSet;
/** position manager */
private PositionRef.Manager positionManager;
/** The string which will be appended to the name of top component
* when top component becomes modified */
protected String modifiedAppendix = " *"; // NOI18N
/** last selected editor. */
private transient Editor lastSelected;
/** Lock acquired after the first modification and used in save */
private FileLock fileLock;
/** Listening on the external changes in the files */
private FileChangeListener fileChangeL;
/** The time of the last save to determine the real external modifications */
private long lastSaveTime;
/** Support an existing loader entry. The file is taken from the
* entry and is updated if the entry is moved or renamed.
* @param entry entry to create instance from
*/
public EditorSupport(MultiDataObject.Entry entry) {
super (entry);
// System.out.println("ES created for " + getEntry().getFile()); // NOI18N
// Thread.dumpStack();
}
/* A method to create a new component. Overridden in subclasses.
* @return the {@link Editor} for this support
*/
protected CloneableTopComponent createCloneableTopComponent () {
// initializes the document if not initialized
prepareDocument ();
DataObject obj = findDataObject ();
Editor editor = new Editor (obj);
return editor;
}
/** Create an undo/redo manager.
* This manager is then attached to the document, and listens to
* all changes made in it.
* <P>
* The default implementation simply uses <code>UndoRedo.Manager</code>.
*
* @return the undo/redo manager
*/
protected UndoRedo.Manager createUndoRedoManager () {
return new UndoRedo.Manager ();
}
/** Getter for undo redo manager. Used only inside this class.
*/
UndoRedo.Manager getUndoRedo () {
if (undoRedo == null) {
synchronized (this) {
if (undoRedo == null) {
undoRedo = createUndoRedoManager ();
}
}
}
return undoRedo;
}
// editor cookie .......................................................................
/** Closes all opened editors (if the user agrees) and
* flushes content of the document to the file.
*
* @return <code>false</code> if the operation is cancelled
*/
public boolean close () {
synchronized (getLock ()) {
if (doc == null) {
return true;
}
if (!super.close ()) {
// if not all editors has been closed
return false;
}
closeDocument();
return true;
}
}
/** Clears all data from memory.
*/
private void closeDocument () {
if (loadTask == null)
return;
loadTask = null;
prepareTask = null;
originalDoc = null;
// listen to modifs
if (listenToModifs) {
getModifL().clearSaveCookie();
if (doc != null) {
doc.removeDocumentListener(getModifL());
}
}
if (fileChangeL != null) {
getEntry().getFile().removeFileChangeListener(fileChangeL);
fileChangeL = null;
}
if (doc != null) {
getUndoRedo().discardAllEdits();
doc.removeUndoableEditListener (getUndoRedo ());
doc = null;
}
if (positionManager != null) {
positionManager.documentClosed ();
fireStateChangeEvent ();
}
kit = null;
updateLineSet (true);
}
/** Load the document into memory. This is done
* in different thread. A task for the thread is returned
* so anyone may test whether the loading has been finished
* or is still in process.
*
* @return task for control over loading
*/
public synchronized Task prepareDocument () {
if (prepareTask != null)
return prepareTask;
// after call to this method the originalDoc and kit are initialized
// in spite of that the document is not yet fully read in
kit = createEditorKit ();
if (originalDoc == null) {
originalDoc = kit.createDefaultDocument ();
}
if (fileChangeL == null) {
fileChangeL = new FileChangeAdapter() {
public void fileChanged(final FileEvent evt) {
if (evt.getTime() > lastSaveTime) {
// post in AWT event thread because of possible dialog popup
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
checkReload(evt);
}
}
);
}
}
};
entry.getFile().addFileChangeListener(fileChangeL);
}
loadTask = new Task (new Runnable () {
public void run () {
synchronized (getLock ()) {
try {
doc = loadDocument (kit, originalDoc);
} finally {
if (doc == null) {
// when this task is finished => the document is never null
doc = createNetBeansDocument (originalDoc);
}
}
// opening the document, inform position manager
getPositionManager ().documentOpened (doc);
// create new description of lines
updateLineSet (true);
lastSaveTime = System.currentTimeMillis();
}
}
});
prepareTask = new Task (new Runnable () {
public void run () {
loadTask.run ();
fireStateChangeEvent ();
// listen to modifications if appropriate
if (listenToModifs) {
doc.addDocumentListener(getModifL());
}
}
});
// runs the initialization task and then
// notifies that the document has been opened
Thread r = new Thread (prepareTask);
// starts the task
r.start ();
return prepareTask;
}
private void checkReload(FileEvent evt) {
boolean doReload = evt.isExpected();
if (!doReload) {
MessageFormat fmt = new MessageFormat(NbBundle.getBundle(EditorSupport.class).getString("FMT_External_change")); // NOI18N
String msg = fmt.format(new Object[] { getEntry().getFile().getPackageNameExt('/', '.')});
NotifyDescriptor nd = new NotifyDescriptor.Confirmation(msg, NotifyDescriptor.YES_NO_OPTION);
Object ret = TopManager.getDefault().notify(nd);
if (NotifyDescriptor.YES_OPTION.equals(ret)) {
doReload = true;
}
}
if (doReload) {
reloadDocument();
}
}
/** Get the document associated with this cookie.
* It is an instance of Swing's {@link StyledDocument} but it should
* also understand the NetBeans {@link NbDocument#GUARDED} to
* prevent certain lines from being edited by the user.
* <P>
* If the document is not loaded the method blocks until
* it is.
*
* @return the styled document for this cookie that
* understands the guarded attribute
* @exception IOException if the document could not be loaded
*/
public StyledDocument openDocument () throws IOException {
for (;;) {
// load the document
prepareDocument ().waitFinished ();
StyledDocument d = doc;
if (d != null)
return d;
}
}
/** Get the document. This method may be called before the document initialization
* (<code>prepareTask</code>)
* has been completed, in such a case the document must not be modified.
* @return document or <code>null</code> if it is not yet loaded
*/
public StyledDocument getDocument () {
for (;;) {
Task t = loadTask;
if (t != null) {
// if an task exists
t.waitFinished ();
return doc;
} else {
return null;
}
}
}
/** Test whether the document is in memory, or whether loading is still in progress.
* @return <code>true</code> if document is loaded
*/
public boolean isDocumentLoaded() {
return loadTask != null;
}
/** Save the document in this thread.
* Create 'orig' document for the case that the save would fail.
* @exception IOException on I/O error
*/
public void saveDocument () throws IOException {
final FileObject file = entry.getFile();
file.getFileSystem ().runAtomicAction (
new org.openide.filesystems.FileSystem.AtomicAction () {
public void run () throws IOException {
FileObject dir = file.getParent();
StyledDocument myDoc = getDocument();
OutputStream os = null;
if (file.isReadOnly()) { // source is read-only
MessageFormat format = new MessageFormat(NbBundle.getBundle(EditorSupport.class).getString("EXC_ReadOnlyFile")); // NOI18N
String msg = format.format(new Object[] { file.getPackageNameExt('/', '.')});
throw new IOException(msg);
}
// write the document
try {
acquireFileLock();
os = new BufferedOutputStream(file.getOutputStream(fileLock));
saveFromKitToStream (myDoc, kit, os);
DataObject dataObj = findDataObject();
dataObj.setModified(false);
releaseFileLock();
} catch (BadLocationException ex) {
TopManager.getDefault ().notifyException (ex);
} finally {
if (os != null)
os.close();
}
lastSaveTime = System.currentTimeMillis();
}
}
);
// update cached info about lines
updateLineSet (true);
}
/**
* Actually write file data to an output stream from an editor kit's document.
* Called during a file save by {@link #saveDocument}.
* <p>The default implementation just calls {@link EditorKit#write(OutputStream, Document, int, int) EditorKit.write(...)}.
* Subclasses could override this to provide support for persistent guard blocks, for example.
* @param doc the document to write from
* @param kit the associated editor kit
* @param stream the open stream to write to
* @throws IOException if there was a problem writing the file
* @throws BadLocationException should not normally be thrown
* @see #loadFromStreamToKit
*/
protected void saveFromKitToStream (StyledDocument doc, EditorKit kit, OutputStream stream) throws IOException, BadLocationException {
kit.write(stream, doc, 0, doc.getLength());
}
/** Test whether the document is modified.
* @return <code>true</code> if the document is in memory and is modified;
* otherwise <code>false</code>
*/
public boolean isModified () {
DataObject dataObj = findDataObject();
return dataObj == null ? false : dataObj.isModified();
}
// Position management methods
/** Provides access to position manager for the document.
* It maintains a set of positions even the document is in memory
* or is on the disk.
*
* @return position manager
*/
final PositionRef.Manager getPositionManager () {
if (positionManager == null) {
synchronized (this) {
if (positionManager == null) {
positionManager = new PositionRef.Manager (this);
}
}
}
return positionManager;
}
/** Finds data object the entry belongs to.
* @return data object or null
*/
protected MultiDataObject findDataObject () {
return entry.getDataObject ();
}
/** Create a position reference for the given offset.
* The position moves as the document is modified and
* reacts to closing and opening of the document.
*
* @param offset the offset to create position at
* @param bias the Position.Bias for new creating position.
* @return position reference for that offset
*/
public final PositionRef createPositionRef (int offset, Position.Bias bias) {
return new PositionRef (getPositionManager (), offset, bias);
}
// LineSet methods .....................................................................
/** Updates the line set.
* @param clear clear any cached set?
* @return the set
*/
Line.Set updateLineSet (boolean clear) {
if (lineSet != null && !clear) {
return lineSet;
}
if (doc == null) {
lineSet = new EditorSupportLineSet.Closed (this);
} else {
lineSet = new EditorSupportLineSet (this, doc);
}
return lineSet;
}
/** Get the line set for all paragraphs in the document.
* @return positions of all paragraphs on last save
*/
public Line.Set getLineSet () {
return updateLineSet (false);
}
// other public methods ................................................................
/**
* Set the MIME type for the document.
* @param s the new MIME type
*/
public void setMIMEType (String s) {
mimeType = s;
}
/**
* Set actions for toolbar.
* @param actions list of actions
*/
public void setActions (SystemAction[] actions) {
this.actions = actions;
}
/** Creates editor kit for this source.
* @return editor kit
*/
protected EditorKit createEditorKit () {
if (kit != null) return kit;
if (mimeType != null) {
kit = JEditorPane.createEditorKitForContentType (mimeType);
} else {
FileObject fo = entry.getFile ();
defaultMIMEType = fo.getMIMEType ();
kit = JEditorPane.createEditorKitForContentType (defaultMIMEType);
}
if (kit == null || kit.getClass () == javax.swing.text.DefaultEditorKit.class) {
kit = JEditorPane.createEditorKitForContentType ("text/plain"); // NOI18N
}
if (kit == null || kit.getClass () == javax.swing.text.DefaultEditorKit.class) {
kit = new PlainEditorKit ();
}
return kit;
}
/** Utility method which enables or disables listening to modifications
* on asociated document.
* <P>
* Could be useful if we have to modify document, but do not want the
* Save and Save All actions to be enabled/disabled automatically.
* Initially modifications are listened to.
* @param listenToModifs whether to listen to modifications
*/
public void setModificationListening (final boolean listenToModifs) {
if (this.listenToModifs == listenToModifs) return;
this.listenToModifs = listenToModifs;
if (doc == null) return;
if (listenToModifs)
doc.addDocumentListener(getModifL());
else
doc.removeDocumentListener(getModifL());
}
/** Adds a listener for status changes. An event is fired
* when the document is moved or removed from memory.
* @param l new listener
*/
public synchronized void addChangeListener (ChangeListener l) {
if (listeners == null)
listeners = new HashSet (8);
listeners.add (l);
}
/** Removes a listener for status changes.
* @param l listener to remove
*/
public synchronized void removeChangeListener (ChangeListener l) {
if (listeners != null)
listeners.remove (l);
}
/** The implementation of @see org.openide.cookies.PrintCookie#print() method. */
public void print() {
PrinterJob job = PrinterJob.getPrinterJob();
try {
Object o = NbDocument.findPageable(openDocument());
if (o instanceof Pageable) {
job.setPageable((Pageable) o);
} else {
PageFormat pf = job.pageDialog(job.defaultPage());
job.setPrintable((Printable) o, pf);
}
if (job.printDialog()) {
job.print();
}
} catch (IOException e) {
TopManager.getDefault().notifyException(e);
} catch (PrinterAbortException e) { // user exception
java.awt.EventQueue.invokeLater(new Runnable() { // display in the awt thread
public void run() {
String msg = NbBundle.getBundle(EditorSupport.class).getString("CTL_Printer_Abort"); // NOI18N
TopManager.getDefault().notify(new NotifyDescriptor.Message(msg));
}
});
} catch (PrinterException e) {
TopManager.getDefault().notifyException(e);
}
}
// non-public functions ................................................................
private synchronized SavingManager getModifL () {
if (modifL == null) {
modifL = new SavingManager();
DataObject dataObj = findDataObject();
// listens whether add or remove SaveCookie
dataObj.addPropertyChangeListener(modifL);
}
return modifL;
}
/** Loads the document for this object.
* @param kit kit to use
* @param d original document to load data into
* @return netbeans document to put around the original one
*/
private StyledDocument loadDocument (EditorKit kit, Document d) {
StyledDocument doc = createNetBeansDocument (d);
FileObject fo = entry.getFile ();
try {
InputStream is = new BufferedInputStream(fo.getInputStream ());
try {
// read the document
loadFromStreamToKit (doc, is, kit);
} finally {
is.close ();
}
// attach undo/redo manager
doc.addUndoableEditListener (getUndoRedo ());
// set document name property
doc.putProperty(javax.swing.text.Document.TitleProperty,
fo.getPackageNameExt('/', '.'));
// set dataobject to stream desc property
doc.putProperty(javax.swing.text.Document.StreamDescriptionProperty,
findDataObject());
} catch (BadLocationException ex) {
TopManager.getDefault ().notifyException (ex);
} catch (IOException ex) {
TopManager.getDefault ().notifyException (ex);
}
return doc;
}
/**
* Actually read file data into an editor kit's document from an input stream.
* Called during a file load by {@link #prepareDocument}.
* <p>The default implementation just calls {@link EditorKit#read(InputStream, Document, int) EditorKit.read(...)}.
* Subclasses could override this to provide support for persistent guard blocks, for example.
* @param doc the document to read into
* @param stream the open stream to read from
* @param kit the associated editor kit
* @throws IOException if there was a problem reading the file
* @throws BadLocationException should not normally be thrown
* @see #saveFromKitToStream
*/
protected void loadFromStreamToKit (StyledDocument doc, InputStream stream, EditorKit kit) throws IOException, BadLocationException {
kit.read(stream, doc, 0);
}
private void acquireFileLock() throws IOException {
if (fileLock == null || !fileLock.isValid()) {
fileLock = getEntry().takeLock();
}
}
private void releaseFileLock() {
if (fileLock != null && fileLock.isValid()) {
fileLock.releaseLock();
}
}
protected void reloadDocument() {
if (doc != null) {
synchronized (this) {
if (listenToModifs) {
doc.removeDocumentListener(getModifL());
getModifL().clearSaveCookie();
}
// Remember caret positions in all opened panes
final JEditorPane[] panes = getOpenedPanes();
final int[] carets;
if (panes != null) {
carets = new int[panes.length];
for(int i = 0; i < panes.length; i++) {
carets[i] = panes[i].getCaretPosition();
}
} else {
carets = new int[0];
}
NbDocument.runAtomic(doc,
new Runnable() {
public void run() {
try {
doc.remove(0, doc.getLength()); // remove all text
} catch (BadLocationException e) {
if (System.getProperty("netbeans.debug.exceptions") != null) // NOI18N
e.printStackTrace();
}
}
}
);
getUndoRedo().discardAllEdits(); // reset undo manager
prepareTask = null;
final Task docLoadTask = prepareDocument();
// Restore caret positions
Thread caretRestoreThread = new Thread() {
public void run() {
docLoadTask.waitFinished();
for (int i = 0; i < panes.length; i++) {
try {
panes[i].setCaretPosition(carets[i]);
} catch (Exception caretException) {
}
}
}
};
caretRestoreThread.start();
updateLineSet(true);
}
}
}
/** Creates netbeans document for a given document.
* @param d document to use as underlaying one
* @return styled document that could support Guarded.ATTRIBUTE
*/
private static StyledDocument createNetBeansDocument (Document d) {
if (d instanceof StyledDocument) {
return (StyledDocument)d;
} else {
// create filter
return new FilterDocument (d);
}
}
/**
* @associates Action
*/
Hashtable kitActions;
private Action getAction (String s) {
if (kitActions == null) {
Action[] a = kit.getActions ();
kitActions = new Hashtable (a.length);
int i, k = a.length;
for (i = 0; i < k; i++)
kitActions.put (a [i].getValue (Action.NAME), a [i]);
}
return (Action) kitActions.get (s);
}
/** Fires a status change event to all listeners. */
private final void fireStateChangeEvent() {
if (listeners != null) {
ChangeEvent event = new ChangeEvent(this);
HashSet s;
synchronized (this) {
s = ((HashSet)listeners.clone ());
}
Iterator it = s.iterator ();
while (it.hasNext ()) {
ChangeListener l = (ChangeListener) it.next();
l.stateChanged(event);
}
}
}
/** Method for access to entry.
*/
MultiDataObject.Entry getEntry () {
return entry;
}
/** If one or more editors are opened finds one.
* @return an editor or null if none is opened
*/
Editor getAnyEditor () {
try {
return (Editor)allEditors.getAnyComponent ();
} catch (java.util.NoSuchElementException e) {
return null;
}
}
/** Forcibly create one editor component. Then set the caret
* to the given position.
* @param pos where to place the caret
* @return always non-<code>null</code> editor
*/
protected Editor openAt(PositionRef pos) {
return openAt(pos, -1);
}
/** Forcibly create one editor component. Then set the caret
* to the given position.
* @param pos where to place the caret
* @return always non-<code>null</code> editor
*/
final Editor openAt(PositionRef pos, int column) {
Editor e = (Editor) openCloneableTopComponent();
e.open();
int offset;
if (column >= 0) {
javax.swing.text.Element el = NbDocument.findLineRootElement (getDocument ());
el = el.getElement (el.getElementIndex (pos.getOffset ()));
offset = el.getStartOffset () + column;
if (offset > el.getEndOffset ()) {
offset = el.getEndOffset ();
}
} else {
offset = pos.getOffset ();
}
prepareDocument ().waitFinished ();
e.pane.getCaret().setDot(offset);
return e;
}
/** Access to lock on operations on the support
*/
Object getLock () {
return allEditors;
}
/** Should test whether all data is saved, and if not, prompt the user
* to save.
*
* @return <code>true</code> if everything can be closed
*/
protected boolean canClose () {
SaveCookie savec = (SaveCookie) entry.getDataObject().getCookie(SaveCookie.class);
if (savec != null) {
MessageFormat format = new MessageFormat(NbBundle.getBundle(EditorSupport.class).getString("MSG_SaveFile")); // NOI18N
String msg = format.format(new Object[] { entry.getDataObject().getName()});
NotifyDescriptor nd = new NotifyDescriptor.Confirmation(msg, NotifyDescriptor.YES_NO_CANCEL_OPTION);
Object ret = TopManager.getDefault().notify(nd);
if (NotifyDescriptor.CANCEL_OPTION.equals(ret)
|| NotifyDescriptor.CLOSED_OPTION.equals(ret)
) {
return false;
}
if (NotifyDescriptor.YES_OPTION.equals(ret)) {
try {
savec.save();
}
catch (IOException e) {
TopManager.getDefault().notifyException(e);
return false;
}
}
}
return true;
}
/* List of all JEditorPane's opened by this editor support.
* The first item in the array should represent the component
* that is currently selected or has been selected lastly.
*
* @return array of panes or null if no pane is opened.
* In no case empty array is returned.
*/
public JEditorPane[] getOpenedPanes () {
LinkedList ll = new LinkedList ();
Enumeration en = allEditors.getComponents ();
while (en.hasMoreElements ()) {
Object o = en.nextElement ();
if (o instanceof Editor) {
Editor ed = (Editor)o;
if (lastSelected == ed) {
ll.addFirst (ed.pane);
} else {
ll.add (ed.pane);
}
}
}
return ll.isEmpty () ?
null : (JEditorPane[])ll.toArray (new JEditorPane[ll.size ()]);
}
/** Cloneable top component to hold the editor kit.
*/
public static class Editor extends CloneableTopComponent {
/** editor pane */
protected JEditorPane pane;
/** data object to work with */
protected DataObject obj;
/** Listener for copy action enabling */
private PropertyChangeListener copyL;
/** Listener for cut action enabling */
private PropertyChangeListener cutL;
/** Listener for data object's save cookie changes */
private PropertyChangeListener saveCookieL;
/** Asociated editor support */
private EditorSupport support;
/** ungle helper variable for deserialization - indicates whether
* top component should be discarded during deserialization or not */
private boolean discard = false;
/** keeps the instance of focus event for manipulating with the
* caret visibility */
private static WeakReference mutableFocusEvent = new WeakReference (null);
static final long serialVersionUID =-185739563792410059L;
/** For externalization of subclasses only */
public Editor () {
super();
}
/** Constructor
* @param obj data object we belong to. The appropriate editor support is
* acquired as the DataObject's EditorSupport.class cookie.
*/
public Editor (DataObject obj) {
super (obj);
this.obj = obj;
// asociate editor support
support = (EditorSupport)obj.getCookie(EditorSupport.class);
initialize();
}
/** Constructor
* @param obj data object we belong to.
* @param support editor support to use.
*/
public Editor (DataObject obj, EditorSupport support) {
super (obj);
this.obj = obj;
// asociate editor support
this.support = support;
initialize();
}
/** Get context help for this editor pane.
* If the registered editor kit provides a help ID in bean info
* according to the protocol described for {@link InstanceSupport#findHelp},
* then that it used, else general help on the editor is provided.
* @return context help
*/
public HelpCtx getHelpCtx () {
HelpCtx fromKit = InstanceSupport.findHelp (new InstanceSupport.Instance (support.kit));
if (fromKit != null)
return fromKit;
else
return new HelpCtx (Editor.class);
}
/** Performs needed initialization */
private void initialize () {
setCloseOperation (TopComponent.CLOSE_EACH);
setLayout (new BorderLayout ());
pane = new JEditorPane ();
pane.setEditorKit (support.kit);
updateName();
if (support.originalDoc instanceof HTMLDocument) {
try {
((HTMLDocument)support.originalDoc).setBase (
support.getEntry ().getFile ().getURL ());
} catch (FileStateInvalidException e) {
}
pane.addHyperlinkListener ( new HyperlinkListener () {
public void hyperlinkUpdate (HyperlinkEvent e) {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if (e instanceof HTMLFrameHyperlinkEvent) {
HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
HTMLDocument doc = (HTMLDocument)pane.getDocument();
doc.processHTMLFrameHyperlinkEvent(evt);
} else {
try {
pane.setPage(e.getURL());
} catch (Exception ex) {
if (System.getProperty ("netbeans.debug.exceptions") != null) ex.printStackTrace(); // NOI18N
}
}
}
}
});
if (support.actions != null)
add (SystemAction.createToolbarPresenter (support.actions),
BorderLayout.NORTH);
}
// listen to save cookie changes in asociated data object
saveCookieL = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (DataObject.PROP_COOKIE.equals(evt.getPropertyName()) ||
DataObject.PROP_NAME.equals(evt.getPropertyName())) {
updateName();
}
}
};
this.obj.addPropertyChangeListener(WeakListener.propertyChange(saveCookieL, this.obj));
pane.setDocument (support.originalDoc);
if (support.originalDoc instanceof NbDocument.CustomEditor) {
add(((NbDocument.CustomEditor)support.originalDoc).createEditor(pane),
BorderLayout.CENTER);
} else { // not custom editor
add (new JScrollPane (pane), BorderLayout.CENTER);
}
}
/* Is called from the clone method to create new component from this one.
* This implementation only clones the object by calling super.clone method.
* @return the copy of this object
*/
protected CloneableTopComponent createClonedObject () {
return new Editor (support.findDataObject());
}
/** Overrides superclass version. Opens top component only if
* it is in valid state.
* (Editor top component may become invalid after deserialization).<br>
* Also tries to open all other top components which are docked
* in editor mode on given workspace, but not visible.<br>
*/
public void open (Workspace workspace) {
if (!discard) {
Workspace realWorkspace = (workspace == null)
? TopManager.getDefault().getWindowManager().getCurrentWorkspace()
: workspace;
dockIfNeeded(realWorkspace);
boolean modeVisible = false;
TopComponent[] tcArray = editorMode(realWorkspace).getTopComponents();
for (int i = 0; i < tcArray.length; i++) {
if (tcArray[i].isOpened(realWorkspace)) {
modeVisible = true;
break;
}
}
if (!modeVisible) {
openOtherEditors(realWorkspace);
}
super.open(workspace);
openOnOtherWorkspaces(realWorkspace);
}
}
/** Dock this top component to editor mode if it is not docked
* in some mode at this time */
private void dockIfNeeded (Workspace workspace) {
// dock into editor mode if possible
Mode ourMode = workspace.findMode(this);
if (ourMode == null) {
editorMode(workspace).dockInto(this);
}
}
private Mode editorMode (Workspace workspace) {
Mode ourMode = workspace.findMode(this);
if (ourMode == null) {
ourMode = workspace.createMode(
EDITOR_MODE, getName(),
EditorSupport.class.getResource(
"/org/openide/resources/editorMode.gif" // NOI18N
)
);
}
return ourMode;
}
/** Utility method, opens this top component on all workspaces
* where editor mode is visible and which differs from given
* workspace. */
private void openOnOtherWorkspaces (Workspace workspace) {
Workspace[] workspaces =
TopManager.getDefault().getWindowManager().getWorkspaces();
Mode curEditorMode = null;
Mode tcMode = null;
for (int i = 0; i < workspaces.length; i++) {
// skip given workspace
if (workspaces[i].equals(workspace)) {
continue;
}
curEditorMode = workspaces[i].findMode(EDITOR_MODE);
tcMode = workspaces[i].findMode(this);
if (
!isOpened(workspaces[i]) &&
curEditorMode != null &&
(
tcMode == null ||
tcMode.equals(curEditorMode)
)
) {
// candidate for opening, but mode must be already visible
// (= some opened top component in it)
TopComponent[] tcArray = curEditorMode.getTopComponents();
for (int j = 0; j < tcArray.length; j++) {
if (tcArray[j].isOpened(workspaces[i])) {
// yep, open this top component on found workspace too
pureOpen(this, workspaces[i]);
break;
}
}
}
}
}
/** Utility method, opens top components which are opened
* in editor mode on some other workspace.
* This method should be called only if first top component is
* being opened in editor mode on given workspace */
private void openOtherEditors (Workspace workspace) {
// choose candidates for opening
Set topComps = new HashSet(15);
Workspace[] wsArray =
TopManager.getDefault().getWindowManager().getWorkspaces();
Mode curEditorMode = null;
TopComponent[] tcArray = null;
for (int i = 0; i < wsArray.length; i++) {
curEditorMode = wsArray[i].findMode(EDITOR_MODE);
if (curEditorMode != null) {
tcArray = curEditorMode.getTopComponents();
for (int j = 0; j < tcArray.length; j++) {
if (tcArray[j].isOpened(wsArray[i])) {
topComps.add(tcArray[j]);
}
}
}
}
// open choosed candidates
for (Iterator iter = topComps.iterator(); iter.hasNext(); ) {
pureOpen((TopComponent)iter.next(), workspace);
}
}
/** Utility method, calls super version of open if given
* top component is of Editor type, or calls regular open otherwise.
* The goal is to prevent from cycle open call between
* Editor top components */
private void pureOpen (TopComponent tc, Workspace workspace) {
if (tc instanceof Editor) {
((Editor)tc).dockIfNeeded(workspace);
((Editor)tc).superOpen(workspace);
} else {
tc.open(workspace);
}
}
private void superOpen (Workspace workspace) {
super.open(workspace);
}
/** Make sure the EditorKit knows it is going bye-bye. */
public boolean canClose (Workspace workspace, boolean last) {
boolean result = super.canClose(workspace, last);
if (result) {
pane.setDocument (pane.getEditorKit ().createDefaultDocument ());
pane.setEditorKit (null);
removeAll ();
}
return result;
}
/** When closing last view, also close the document.
* @return <code>true</code> if close succeeded
*/
protected boolean closeLast () {
if (!support.canClose ()) {
// if we cannot close the last window
return false;
}
support.closeDocument ();
if (support.lastSelected == this) {
support.lastSelected = null;
}
return true;
}
/** The undo/redo manager of the support.
* @return the undo/redo manager shared by all editors for this support
*/
public UndoRedo getUndoRedo () {
return support.getUndoRedo ();
}
/* Returns an array of system actions which will appear in
* the popup menu of this component.<P>
* Subclasses are encouraged to override this method to specify
* their own set of actions.<br>
* Remember to call superclass when overriding and add your actions
* to superclass ones (or add superclass actions to yours),
* because default implementation provides support for standard
* component actions like save, close, and clone.
* @return an array of actions of this component
*/
public SystemAction[] getSystemActions () {
SystemAction[] sa = super.getSystemActions ();
if (support.actions == null) return sa;
return SystemAction.linkActions (sa, support.actions);
}
/** Transfer the focus to the editor pane.
*/
public void requestFocus () {
super.requestFocus ();
pane.requestFocus ();
}
/** @return Preferred size of editor top component */
public Dimension getPreferredSize () {
Rectangle bounds = TopManager.getDefault().getWindowManager().
getCurrentWorkspace().getBounds();
return new Dimension(bounds.width / 2, bounds.height / 2);
}
/* This method is called when parent window of this component has focus,
* and this component is preferred one in it.
* Override this method to perform special action on component activation.
* (Typical thing to do here is set performers for your actions)
* Remember to call superclass to
*/
protected void componentActivated () {
final Action copy = support.getAction (DefaultEditorKit.copyAction);
if (copy != null) {
final CallbackSystemAction sysCopy
= ((CallbackSystemAction) SystemAction.get (CopyAction.class));
sysCopy.setActionPerformer (
new ActionPerformer () {
public void performAction (SystemAction action) {
copy.actionPerformed (new ActionEvent (Editor.this, 0, "")); // NOI18N
}
}
);
copy.addPropertyChangeListener(
copyL = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("enabled".equals(evt.getPropertyName())) { // NOI18N
sysCopy.setEnabled(((Boolean)evt.getNewValue()).booleanValue());
}
}
}
);
sysCopy.setEnabled(copy.isEnabled());
}
final Action cut = support.getAction (DefaultEditorKit.cutAction);
if (cut != null) {
final CallbackSystemAction sysCut
= ((CallbackSystemAction) SystemAction.get (CutAction.class));
sysCut.setActionPerformer (
new ActionPerformer () {
public void performAction (SystemAction action) {
cut.actionPerformed (new ActionEvent (Editor.this, 0, "")); // NOI18N
}
}
);
cut.addPropertyChangeListener(
cutL = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("enabled".equals(evt.getPropertyName())) { // NOI18N
sysCut.setEnabled(((Boolean)evt.getNewValue()).booleanValue());
}
}
}
);
sysCut.setEnabled(cut.isEnabled());
}
final Action paste = support.getAction (DefaultEditorKit.pasteAction);
if (paste != null) {
PasteAction sysPaste
= ((PasteAction) SystemAction.get (PasteAction.class));
sysPaste.setPasteTypes (
new PasteType[] {
new PasteType () {
public Transferable paste () {
paste.actionPerformed (new ActionEvent (Editor.this, 0, "")); // NOI18N
return null;
}
}
}
);
}
// commented as this caused some problems in focus managing
//pane.requestFocus ();
support.lastSelected = this;
// HACKING focus problems
// to prevent from bug #3432 (cursor is invisible)
// beware, this is UGLY code, ideas welcomed
SwingUtilities.invokeLater(new Runnable() {
public void run () {
Caret ourCaret = pane.getCaret();
if (ourCaret instanceof FocusListener) {
((FocusListener)ourCaret).focusGained(mutableFocusEvent(Editor.this));
}
}
});
}
/*
* This method is called when parent window of this component losts focus,
* or when this component losts preferrence in the parent window.
* Override this method to perform special action on component deactivation.
* (Typical thing to do here is unset performers for your actions)
*/
protected void componentDeactivated () {
final Action copy = support.getAction (DefaultEditorKit.copyAction);
if (copy != null) {
copy.removePropertyChangeListener(copyL);
}
final Action cut = support.getAction (DefaultEditorKit.cutAction);
if (cut != null) {
cut.removePropertyChangeListener(cutL);
}
// HACKING focus problems
// to prevent from bug #3432 (cursor is invisible)
// beware, this is UGLY code, ideas welcomed
SwingUtilities.invokeLater(new Runnable() {
public void run () {
Caret ourCaret = pane.getCaret();
if (ourCaret instanceof FocusListener) {
((FocusListener)ourCaret).focusLost(mutableFocusEvent(Editor.this));
}
}
});
}
/** Updates the name of this top component according to
* the existence of the save cookie in ascoiated data object.
* Updates tooltip also.
*/
protected void updateName () {
// update name
String name = Editor.this.obj.getNodeDelegate().getDisplayName();
if (Editor.this.obj.getCookie(SaveCookie.class) != null)
setName(name + support.modifiedAppendix);
else
setName(name);
// update tooltip
FileObject fo = support.getEntry().getFile();
StringBuffer fullName = new StringBuffer(fo.getPackageName('.'));
String extension = fo.getExt();
if (extension.length() > 0) {
fullName.append(" ["); // NOI18N
fullName.append(extension);
fullName.append(']');
}
setToolTipText(fullName.toString());
}
/* Serialize this top component.
* @param out the stream to serialize to
*/
public void writeExternal (ObjectOutput out)
throws IOException {
super.writeExternal(out);
// save our data object
out.writeObject(obj);
// save cursor position
out.writeObject(new Integer(pane.getCaret().getDot()));
// save editor support if it is serializable
if (support instanceof Serializable) {
out.writeObject(support);
}
else {
out.writeObject(null);
}
// System.out.println(getName() + " written!"); // NOI18N
}
/* Deserialize this top component.
* @param in the stream to deserialize from
*/
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException {
// System.out.println("Reading editor TC..."); // NOI18N
super.readExternal(in);
// read data object
obj = (DataObject)in.readObject();
// load cursor position
int offset = ((Integer)in.readObject()).intValue();
// try to load or associate editor support
Object maybe = in.readObject();
if (maybe instanceof EditorSupport)
support = (EditorSupport)maybe;
else
support = (EditorSupport)obj.getCookie(EditorSupport.class);
// ensure that file still can be accessed
FileObject fo = support.getEntry().getFile();
try {
InputStream is = fo.getInputStream();
is.close();
} catch (FileNotFoundException exc) {
// warn user
String message = NbBundle.getBundle(EditorSupport.class).
getString("FMT_FileNotFound"); // NOI18N
TopManager.getDefault().getStdOut().println(
MessageFormat.format(message, new Object [] {
fo.getPackageNameExt('/', '.')
})
);
// signalize to readResolve method that we should
// deserialize to null -> this will encourage window system
// to discard this top component
discard = true;
return;
//throw new org.openide.util.io.SafeException(exc);
}
support.openDocument();
initialize();
pane.getCaret().setDot(offset);
// System.out.println(getName() + " read!"); // NOI18N
}
protected Object readResolve ()
throws ObjectStreamException {
return discard ? null : this;
}
/** Accessor to the mutable focus event. We keep the focus
* event as mutable, because focus chnages may come frequently
* and there is no need to create the venet again and again. */
private static FocusEvent mutableFocusEvent (Component source) {
FocusEvent ev = (FocusEvent)mutableFocusEvent.get ();
if (ev == null) {
ev = new FocusEvent(
source,
FocusEvent.FOCUS_FIRST + FocusEvent.FOCUS_LAST
);
mutableFocusEvent = new WeakReference (ev);
}
return ev;
}
} // end of Editor inner class
/** Default editor kit.
*/
private static final class PlainEditorKit extends DefaultEditorKit
implements ViewFactory {
static final long serialVersionUID =-5788777967029507963L;
/** @return cloned instance
*/
public Object clone () {
return new PlainEditorKit ();
}
/** @return this (I am the ViewFactory)
*/
public ViewFactory getViewFactory() {
return this;
}
/** Plain view for the element
*/
public View create(Element elem) {
return new WrappedPlainView(elem);
}
}
/** SavingManager manages two tasks concerning saving:<P>
* 1) It tracks changes in document asociated with data object and
* sets modification flag appropriately.<P>
* 2) This class also implements functionality of SaveCookie interface
*/
private final class SavingManager implements DocumentListener, SaveCookie, PropertyChangeListener {
/*********** Implementation of the DocumentListener *******/
/** Gives notification that an attribute or set of attributes changed.
* @param ev event describing the action
*/
public void changedUpdate(DocumentEvent ev) {
//modified(); (bugfix #1492)
}
/** Gives notification that there was an insert into the document.
* @param ev event describing the action
*/
public void insertUpdate(DocumentEvent ev) {
modified();
}
/** Gives notification that a portion of the document has been removed.
* @param ev event describing the action
*/
public void removeUpdate(DocumentEvent ev) {
modified();
}
/** Gives notification that the DataObject was changed.
* @param ev PropertyChangeEvent
*/
public void propertyChange(PropertyChangeEvent ev) {
if (ev.getSource() == findDataObject()) {
if (ev.getPropertyName() == DataObject.PROP_MODIFIED) {
if (((Boolean) ev.getNewValue()).booleanValue()) {
addSaveCookie();
} else {
removeSaveCookie();
}
}
if (ev.getPropertyName().equals(DataObject.PROP_PRIMARY_FILE)) {
FileObject oldF = (FileObject)ev.getOldValue();
FileObject newF = (FileObject)ev.getNewValue();
oldF.removeFileChangeListener(fileChangeL);
releaseFileLock();
newF.addFileChangeListener(fileChangeL);
if (isModified()) { // refresh lock
try {
acquireFileLock();
} catch (IOException e) {
if (System.getProperty("netbeans.debug.exceptions") != null) // NOI18N
e.printStackTrace();
}
}
}
}
}
/******* Implementation of the Save Cookie *********/
public void save () throws IOException {
saveDocument();
}
void clearSaveCookie() {
DataObject dataObj = findDataObject();
// remove save cookie (if save was succesfull)
dataObj.setModified(false);
releaseFileLock();
}
/** Sets modification flag.
*/
private void modified () {
DataObject dataObj = findDataObject();
// try to lock the document first
try {
acquireFileLock();
} catch (IOException e) { // locking failed
Toolkit.getDefaultToolkit().beep();
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
UndoRedo ur = getUndoRedo();
try {
while (ur.canUndo()) {
ur.undo();
}
} catch (CannotUndoException exc) {
}
}
}
);
return;
}
dataObj.setModified(true);
}
/** Adds save cookie to the DO.
*/
private void addSaveCookie() {
DataObject dataObj = findDataObject();
// add Save cookie to the data object
if (dataObj instanceof MultiDataObject) {
if (dataObj.getCookie(SaveCookie.class) == null) {
((MultiDataObject)dataObj).getCookieSet().add(this);
}
}
}
/** Removes save cookie from the DO.
*/
private void removeSaveCookie() {
DataObject dataObj = findDataObject();
// add Save cookie to the data object
if (dataObj instanceof MultiDataObject) {
if (dataObj.getCookie(SaveCookie.class) == this) {
((MultiDataObject)dataObj).getCookieSet().remove(this);
}
}
}
} // end of SavingManager inner class
}
/*
* Log
* 119 src-jtulach1.118 2/25/00 Petr Jiricka Bugfix 5603
* 118 src-jtulach1.117 1/19/00 Miloslav Metelka CLOSED_OPTION checking in
* canClose()
* 117 src-jtulach1.116 1/18/00 Miloslav Metelka reloadDocument() calls
* clearSaveCookie()
* 116 src-jtulach1.115 1/16/00 Jaroslav Tulach Memory leak fix.
* 115 src-jtulach1.114 1/14/00 Miloslav Metelka NOI18N
* 114 src-jtulach1.113 1/13/00 Ian Formanek NOI18N
* 113 src-jtulach1.112 1/10/00 David Simonek invisible cursor bug
* fixed
* 112 src-jtulach1.111 1/5/00 Jaroslav Tulach Fix for
* NullPointerException.
* 111 src-jtulach1.110 1/4/00 Miloslav Metelka undo buffer cleared
* during close()
* 110 src-jtulach1.109 12/23/99 David Simonek special editor mode
* opening
* 109 src-jtulach1.108 12/21/99 David Simonek #4844
* 108 src-jtulach1.107 12/17/99 David Simonek preferred size of editor
* top component fixed
* 107 src-jtulach1.106 12/8/99 Jaroslav Tulach TopComponent enhanced.
* 106 src-jtulach1.105 11/29/99 Miloslav Metelka removing fileChangeL on
* doc close
* 105 src-jtulach1.104 11/26/99 Miloslav Metelka Backup before overwriting
* now supported in filesystem
* 104 src-jtulach1.103 11/25/99 Miloslav Metelka File reloading improved
* 103 src-jtulach1.102 11/5/99 Jaroslav Tulach WeakListener has now
* registration methods.
* 102 src-jtulach1.101 11/4/99 David Simonek tooltips on editor tabs
* 101 src-jtulach1.100 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 100 src-jtulach1.99 10/10/99 Petr Hamernik console debug messages
* removed.
* 99 src-jtulach1.98 10/7/99 David Simonek requst focus removed from
* componentActivated
* 98 src-jtulach1.97 10/7/99 Miloslav Metelka releasing lock when save
* was rejected
* 97 src-jtulach1.96 10/6/99 David Simonek more robust serialization
* of window system (especially editor TCs)
* 96 src-jtulach1.95 10/6/99 Miloslav Metelka blocking modification
* when lock acquiring fails
* 95 src-jtulach1.94 10/6/99 David Simonek repaired deserialization
* when document file not found
* 94 src-jtulach1.93 10/5/99 Miloslav Metelka syncing with external
* changes and modification locking
* 93 src-jtulach1.92 9/27/99 Petr Jiricka Ensured that
* closeDocument() is only called once
* 92 src-jtulach1.91 9/22/99 Miloslav Metelka Undo of the last change
* 91 src-jtulach1.90 9/17/99 Miloslav Metelka Syncing and locking on
* modify
* 90 src-jtulach1.89 9/13/99 Petr Jiricka Loading a document
* divided in two tasks - fixes problems with modifying the document before
* appropriate listeners have been added.
* 89 src-jtulach1.88 9/9/99 Petr Hamernik fixed bug #1922
* 88 src-jtulach1.87 9/1/99 Petr Jiricka Close calls closeDocument
* 87 src-jtulach1.86 8/27/99 Libor Kramolis Try create text/plain
* editor kit before swing editor kit
* 86 src-jtulach1.85 8/19/99 Ian Formanek Fixed bug 3520 -
* Breakpoint cannot be set in Editing wkspc when editor was closed in
* Debugging wkspc.
* 85 src-jtulach1.84 8/17/99 Petr Jiricka Oops ! Debug prints
* 84 src-jtulach1.83 8/17/99 Petr Jiricka Fixed serialization of
* editor top components constructed by the 2-parameter constructor.
* 83 src-jtulach1.82 8/17/99 Ales Novak #3414
* 82 src-jtulach1.81 8/9/99 David Simonek
* 81 src-jtulach1.80 8/9/99 Ian Formanek Generated Serial Version
* UID
* 80 src-jtulach1.79 8/9/99 Miloslav Metelka Save failure messages
* localized
* 79 src-jtulach1.78 7/30/99 Ales Novak an impl of Line.show(int,
* int) does not ignore column now
* 78 src-jtulach1.77 7/29/99 David Simonek further ws serialization
* changes
* 77 src-jtulach1.76 7/28/99 David Simonek workspace serialization
* bugfixes
* 76 src-jtulach1.75 7/28/99 David Simonek canClose updates
* 75 src-jtulach1.74 7/27/99 Jaroslav Tulach Faster lines.
* 74 src-jtulach1.73 7/25/99 Ian Formanek Exceptions printed to
* console only on "netbeans.debug.exceptions" flag
* 73 src-jtulach1.72 7/21/99 Petr Hamernik editor mode is public
* 72 src-jtulach1.71 7/21/99 Ales Novak println removed
* 71 src-jtulach1.70 7/13/99 Ales Novak changed to new win system
* 70 src-jtulach1.69 7/11/99 David Simonek window system change...
* 69 src-jtulach1.68 7/8/99 Jesse Glick Context help.
* 68 src-jtulach1.67 6/30/99 David Simonek rollback, repaired
* 67 src-jtulach1.66 6/30/99 David Simonek bugfix #2369
* 66 src-jtulach1.65 6/22/99 Ales Novak creating of editors is
* centralized
* 65 src-jtulach1.64 6/10/99 Petr Hamernik revision 56 rolled back
* 64 src-jtulach1.63 6/10/99 Miloslav Metelka saveDocument() asks user
* 63 src-jtulach1.62 6/10/99 Jesse Glick [JavaDoc]
* 62 src-jtulach1.61 6/9/99 Ian Formanek Fixed resources for
* package change
* 61 src-jtulach1.60 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 60 src-jtulach1.59 6/8/99 Ian Formanek OK, OK, that was not the
* best thing on the world...
* 59 src-jtulach1.58 6/8/99 Ian Formanek Fixed last change
* 58 src-jtulach1.57 6/8/99 Ian Formanek Survives if kit for
* requested mime type is not registered -> first tries text/plain kit,
* then PlainEditorKit is used
* 57 src-jtulach1.56 6/7/99 Jaroslav Tulach EditorCookie.getOpenedPanes
* ()
* 56 src-jtulach1.55 6/7/99 Jaroslav Tulach Selecting line in
* compiler output does not focuses editor.
* 55 src-jtulach1.54 6/5/99 Jesse Glick [JavaDoc]
* 54 src-jtulach1.53 6/4/99 Petr Jiricka Added constructor which
* explicitly specifies editorsupport to be used
* 53 src-jtulach1.52 6/4/99 Jaroslav Tulach Orig files should not be
* seen in the explorer.
* 52 src-jtulach1.51 6/4/99 Ales Novak # 1970
* 51 src-jtulach1.50 5/17/99 Petr Hamernik fixed bug #1540 -
* canClose() confirmation dialog added
* 50 src-jtulach1.49 5/17/99 Miloslav Metelka closeDocument() non-null
* doc check
* 49 src-jtulach1.48 5/17/99 Miloslav Metelka saveDocument uses "orig"
* file
* 48 src-jtulach1.47 5/16/99 Petr Hamernik open() calling adding to
* method openAt(..)
* 47 src-jtulach1.46 5/15/99 David Simonek deserialization reopening
* bug fixed
* 46 src-jtulach1.45 5/15/99 Petr Hamernik deadlock prevention
* 45 src-jtulach1.44 5/15/99 David Simonek bugfix #1492
* 44 src-jtulach1.43 5/14/99 David Simonek serialization now
* hopefully correct...
* 43 src-jtulach1.42 5/14/99 Ales Novak bugfix #1466
* 42 src-jtulach1.41 5/14/99 David Simonek serizalization
* modifications
* 41 src-jtulach1.40 5/12/99 Ales Novak PrinterAbortException
* handling
* 40 src-jtulach1.39 5/12/99 David Simonek serialization
* 39 src-jtulach1.38 5/11/99 Miloslav Metelka
* 38 src-jtulach1.37 5/11/99 Ales Novak implements PrintCookie
* 37 src-jtulach1.36 4/23/99 Petr Jiricka
* 36 src-jtulach1.35 4/23/99 Jaroslav Tulach Removed setEditable.
* Create uneditable components by subclassing.
* 35 src-jtulach1.34 4/21/99 Petr Hamernik renaming the component
* after rename data object
* 34 src-jtulach1.33 4/21/99 Miloslav Metelka Added CustomEditor
* handling
* 33 src-jtulach1.32 4/9/99 Petr Hamernik closing bugfix
* 32 src-jtulach1.31 4/8/99 David Simonek debigging comments
* removed...
* 31 src-jtulach1.30 4/6/99 David Simonek now supports modified
* flag as suffix to top component's name
* 30 src-jtulach1.29 4/6/99 David Simonek save enabling bugs
* finally fixed (hopefully)
* 29 src-jtulach1.28 4/2/99 Petr Hamernik bugfix
* 28 src-jtulach1.27 3/30/99 Miloslav Metelka Enabling of
* cut/copy/paste actions
* 27 src-jtulach1.26 3/26/99 Jesse Glick [JavaDoc]
* 26 src-jtulach1.25 3/25/99 David Simonek another small changes in
* window system
* 25 src-jtulach1.24 3/20/99 Petr Hamernik
* 24 src-jtulach1.23 3/18/99 Jaroslav Tulach
* 23 src-jtulach1.22 3/18/99 Petr Hamernik
* 22 src-jtulach1.21 3/17/99 Jesse Glick [JavaDoc]
* 21 src-jtulach1.20 3/17/99 Jaroslav Tulach Output Window fixing.
* 20 src-jtulach1.19 3/15/99 Jesse Glick [JavaDoc]
* 19 src-jtulach1.18 3/14/99 Jaroslav Tulach Change of
* MultiDataObject.Entry.
* 18 src-jtulach1.17 3/12/99 David Simonek Editor made public
* 17 src-jtulach1.16 3/11/99 Jaroslav Tulach Undo/Redo support
* 16 src-jtulach1.15 3/11/99 Jaroslav Tulach Works with plain
* document.
* 15 src-jtulach1.14 3/10/99 Petr Hamernik
* 14 src-jtulach1.13 3/10/99 Jaroslav Tulach Creates line set even the
* document is not opened
* 13 src-jtulach1.12 3/9/99 Jaroslav Tulach getlock added
* 12 src-jtulach1.11 3/8/99 Petr Hamernik
* 11 src-jtulach1.10 2/26/99 Jesse Glick Added
* saveFromKitToStream() & loadFromStreamToKit().
* 10 src-jtulach1.9 2/24/99 Jesse Glick [JavaDoc]
* 9 src-jtulach1.8 2/19/99 Petr Hamernik changes with
* Position.Bias
* 8 src-jtulach1.7 2/17/99 Petr Hamernik
* 7 src-jtulach1.6 2/15/99 Jaroslav Tulach Jumps to current line
* 6 src-jtulach1.5 2/11/99 Jaroslav Tulach
* 5 src-jtulach1.4 2/10/99 Jesse Glick [JavaDoc]
* 4 src-jtulach1.3 2/8/99 Petr Hamernik synchonization bugfix
* 3 src-jtulach1.2 2/4/99 Petr Hamernik setting of extended file
* attributes doesn't require FileLock
* 2 src-jtulach1.1 2/3/99 Jaroslav Tulach
* 1 src-jtulach1.0 2/3/99 Jaroslav Tulach
* $
*/